﻿Imports System.Reflection
Imports System.Linq.Expressions
Imports Kairyu.Connection

Namespace Load

    ''' <summary>
    ''' ページングプロバイダー
    ''' </summary>
    ''' <remarks>
    ''' SQLServerのみ対応
    ''' </remarks>
    Public Class PagingQueryProvider
        Inherits QueryProviderBase

        Sub New(provider As QueryProviderBase, sort() As PropertyInfo, pageNo As Integer, pageSize As Integer)
            _loader = provider.Loader
            Me.InnerProvider = provider
            Me.Sort = sort
            Me.PageNo = pageNo
            Me.PageSize = pageSize
        End Sub

        Private Property InnerProvider As QueryProviderBase
        Private Property Sort As PropertyInfo()
        Private Property PageNo As Integer
        Private Property PageSize As Integer

        Public Overrides Function Execute(e As ConstantExpression) As Object
            Dim t As Type = e.Type.GetGenericArguments.First
            Dim q As IQuery = DirectCast(e.Value, IQuery)

            Dim logs As New Dictionary(Of String, Long)
            Dim totalSw As New Stopwatch
            totalSw.Start()
            Dim sw As New Stopwatch
            sw.Start()

            '定義
            Dim def = Me.CreateRootTableAlias(t, q)

            sw.Stop()
            logs.Add("definition", sw.ElapsedMilliseconds)
            sw.Restart()

            Dim lst = Me.CreateGenericList(t)
            lst.PageNo = Me.PageNo
            lst.PageSize = Me.PageSize

            'DBコマンド
            Using cmd As IDbCommand = Me.CreateCommand(def, q)
                sw.Stop()
                logs.Add("command", sw.ElapsedMilliseconds)
                sw.Restart()

                Using totalcmd = CreateTotalCountCommand(cmd)
                    lst.Total = totalcmd.ExecuteScalarWithLog(InfoLevelEnum.Debug)
                End Using

                'レコード件数がない場合、処理を終了
                If lst.Total = 0 Then
                    Log.DoFinishAction(Me, totalSw.ElapsedMilliseconds, 0, logs)
                    Me.Loader.ResetQueryType()
                    Return lst
                End If

                '読み込み
                Dim rows As Integer = 0
                Using pagecmd = Me.CreatePageCommand(def, cmd)
                    Using r As IDataReader = pagecmd.ExecuteReaderWithLog(def.Table.LoadTableName)
                        sw.Stop()
                        logs.Add("query", sw.ElapsedMilliseconds)
                        sw.Restart()

                        While r.Read()
                            lst.Add(Me.CreateModel(def, r))
                            rows += 1
                        End While

                        sw.Stop()
                        totalSw.Stop()
                        logs.Add("mapping", sw.ElapsedMilliseconds)
                    End Using
                End Using

                Log.DoFinishAction(Me, totalSw.ElapsedMilliseconds, rows, logs)
            End Using

            '追加処理
            Me.OnLoaded(lst, t, q)

            '設定を初期値に戻します
            Me.Loader.ResetQueryType()

            Return lst
        End Function

        Private Function CreateGenericList(t As Type) As IPageList
            Dim geneType As Type = GetType(PageList(Of )).MakeGenericType({t})
            Return Activator.CreateInstance(geneType)
        End Function

        Private Function CreateTotalCountCommand(cmd As IDbCommand) As IDbCommand
            Dim c As IDbCommand = Me.Loader.Connection.CreateCommand
            c.Transaction = Me.Loader.Transaction
            c.CommandText = String.Format("SELECT COUNT(*) FROM ({0}) Q", cmd.CommandText)
            For Each item As IDbDataParameter In cmd.Parameters
                Dim p As IDbDataParameter = c.CreateParameter
                p.ParameterName = item.ParameterName
                p.Value = item.Value
                c.Parameters.Add(p)
            Next
            Return c
        End Function

        Private Function CreatePageCommand(def As RootTableAlias, cmd As IDbCommand) As IDbCommand
            Dim c As IDbCommand = cmd.Connection.CreateCommand
            c.Transaction = cmd.Transaction

            'ソート条件の作成
            Dim sortText As String = String.Empty
            Dim addSort As Action(Of String) =
                Sub(s)
                    If String.IsNullOrEmpty(sortText) = False Then sortText &= ", "
                    sortText &= s
                End Sub

            For Each item As PropertyInfo In Me.Sort
                Dim prop = item
                addSort.Invoke((From el In def.ColumnAliases Where el.Column.PropertyInfo.Equals(prop) Select el.ColumnAliasName).First)
            Next

            c.CommandText = String.Format("SELECT * FROM (SELECT ROW_NUMBER() OVER(ORDER BY {1}) AS ROW_NO, Q.* FROM ({0}) Q) Q WHERE Q.ROW_NO BETWEEN {2}ROW_START AND ({2}ROW_START + {2}ROW_TAKE - 1)", cmd.CommandText, sortText, def.ParameterPrefix)

            For Each item As IDbDataParameter In cmd.Parameters
                Dim p As IDbDataParameter = c.CreateParameter
                p.ParameterName = item.ParameterName
                p.Value = item.Value
                c.Parameters.Add(p)
            Next

            Dim start As Integer = Me.PageSize * (Me.PageNo - 1) + 1
            Dim take As Integer = Me.PageSize
            c.Parameters.Add(CreateParameter(c, "ROW_START", start))
            c.Parameters.Add(CreateParameter(c, "ROW_TAKE", take))

            Return c
        End Function

        Protected Friend Overrides Function CreateRootTableAlias(t As System.Type, q As IQuery) As RootTableAlias
            Return Me.InnerProvider.CreateRootTableAlias(t, q)
        End Function

        Protected Friend Overrides Sub OnLoaded(lst As System.Collections.IList, t As System.Type, q As IQuery)
            Me.InnerProvider.OnLoaded(lst, t, q)
        End Sub

        Private Shared Function CreateParameter(ByVal cmd As IDbCommand, ByVal name As String, ByVal val As Object) As IDbDataParameter
            Dim prm As IDbDataParameter = cmd.CreateParameter
            prm.ParameterName = name
            prm.Value = val
            Return prm
        End Function
    End Class

End Namespace
